#include <fstream>
#include <sstream>
#include <iostream>
#include "Poco/Base64Encoder.h"
#include "Poco/Base64Decoder.h"
#include "Poco/StreamCopier.h"
#include "Poco/DigestEngine.h"
#include "Poco/Thread.h"
#include "Poco/Exception.h"
#include "Foundation.h"

using namespace SystemTool;

// Copy stream with block buffer
unsigned long long Foundation::CopyStream(std::istream &is, std::ostream &os, int iBlockSizeForEachCopy, std::function<void(uint64_t)> fProgressFunc)
{
    try
    {
        return Poco::StreamCopier::copyStream(is, os, iBlockSizeForEachCopy, fProgressFunc);
    }
    catch (const Poco::Exception &e)
    {
        std::cerr << "Err Happened:" << e.displayText() << std::endl;
    }
    return 0;
}

// Copy stream to string
unsigned long long Foundation::CopyToString(std::istream &is, std::string &strData, int iBlockSizeForEachCopy)
{
    try
    {
        return Poco::StreamCopier::copyToString(is, strData, iBlockSizeForEachCopy);
    }
    catch (const Poco::Exception &e)
    {
        std::cerr << "Err Happened:" << e.displayText() << std::endl;
    }
    return 0;
}

// Copy stream with block buffer
unsigned long long Foundation::CopyStream64(std::istream &is, std::ostream &os, int iBlockSizeForEachCopy, std::function<void(uint64_t)> fProgressFunc)
{
    try
    {
        return Poco::StreamCopier::copyStream64(is, os, iBlockSizeForEachCopy, fProgressFunc);
    }
    catch (const Poco::Exception &e)
    {
        std::cerr << "Err Happened:" << e.displayText() << std::endl;
    }
    return 0;
}

// Copy stream to string
unsigned long long Foundation::CopyToString64(std::istream &is, std::string &strData, int iBlockSizeForEachCopy)
{
    try
    {
        return Poco::StreamCopier::copyToString64(is, strData, iBlockSizeForEachCopy);
    }
    catch (const Poco::Exception &e)
    {
        std::cerr << "Err Happened:" << e.displayText() << std::endl;
    }
    return 0;
}

// Hex to string
std::string Foundation::ToHexString(std::vector<unsigned char>& vByteArr)
{
    if (vByteArr.empty())
    {
        return "";
    }

    std::string strHexData = "";
    try
    {
        strHexData = Poco::DigestEngine::digestToHex(vByteArr, vByteArr.size());
    }
    catch (const Poco::Exception &e)
    {
        std::cerr << "Err Happened:" << e.displayText() << std::endl;
    }
    return strHexData;
}

// String to hex
bool Foundation::FromHexString(std::string strHexData, std::vector<unsigned char>& vOutByteArr)
{
    if(strHexData.empty())
    {
        return false;
    }

    try
    {
        vOutByteArr = Poco::DigestEngine::digestFromHex(strHexData);
        return true;
    }
    catch (const Poco::Exception &e)
    {
        std::cerr << "Err Happened:" << e.displayText() << std::endl;
    }
    return false;
}

// Base64 a string
std::string Foundation::ToBase64(std::string &strData)
{
    if(strData.empty())
    {
        return "";
    }

    std::string strBase64Data = "";
    try
    {
        std::stringstream ss;

        Poco::Base64Encoder Encoder(ss);
        Encoder.write(strData.c_str(), strData.length());
        Encoder.close();
        strBase64Data = ss.str();
    }
    catch (const Poco::Exception &e)
    {
        std::cerr << "Err Happened:" << e.displayText() << std::endl;
    }
    return strBase64Data;
}

// De-base64 the string
std::string Foundation::FromBase64(std::string &strBase64Data)
{
    if (strBase64Data.empty())
    {
        return "";
    }

    std::string strData = "";
    try
    {
        std::stringstream ss;
        ss << strBase64Data;
        Poco::Base64Decoder Decoder(ss);

        std::stringstream os;
        os << Decoder.rdbuf();
        strData = os.str();
    }
    catch (const Poco::Exception &e)
    {
        std::cerr << "Err Happened:" << e.displayText() << std::endl;
    }
    return strData;
}

// Base64 a file
bool Foundation::ToBase64(std::ifstream &is, std::ostream &os, bool bIsKeepNewLineMode, int iBlockSizeForEachCopy, std::function<void(uint64_t)> fProgressFunc)
{
    if (!is.good() || !os.good())
    {
        return false;
    }

    try
    {
        Poco::Base64Encoder Encoder(os);
        if (!bIsKeepNewLineMode)
        {
            Encoder.rdbuf()->setLineLength(0);
        }
        CopyStream64(is, Encoder, iBlockSizeForEachCopy, fProgressFunc);
        Encoder.close();
    }
    catch (const Poco::Exception &e)
    {
        std::cerr << "Err Happened:" << e.displayText() << std::endl;
        return false;
    }
    return true;
}

// De-Base64 a file
bool Foundation::FromBase64(std::ifstream& is, std::ostream& os, int iBlockSizeForEachCopy, std::function<void(uint64_t)> fProgressFunc)
{
    if (!is.good() || !os.good())
    {
        return false;
    }

    try
    {
        Poco::Base64Decoder Decoder(is);
        CopyStream64(Decoder, os, iBlockSizeForEachCopy, fProgressFunc);
        Decoder.clear();
    }
    catch (const Poco::Exception &e)
    {
        std::cerr << "Err Happened:" << e.displayText() << std::endl;
        return false;
    }
    return true;
}

// Sleep ms
void Foundation::Sleep_Ms(uint64_t iMs)
{
    try
    {
        Poco::Thread::sleep(iMs);
    }
    catch (const Poco::Exception &e)
    {
        std::cerr << "Err Happened:" << e.displayText() << std::endl;
    }
}

// Sleep s
void Foundation::Sleep_S(uint64_t iSecond)
{
    Sleep_Ms(iSecond * 1000);
}

// Sleep us
void Foundation::Sleep_Us(uint64_t iUs)
{
    Sleep_Ms(iUs / 1000);
}
